package com.study.hive.udaf;

import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;

/**
 * UDAF是聚合函数，相当于reduce，将表中多行数据聚合成一行结果
 * UDAF是需要在hive的sql语句和group by联合使用，hive的group by对于每个分组，只能返回一条记录
 * hive有两种UDAF：简单和通用。
 * 简单的UDAF，写的相当简单的，但因为使用Java反射导致性能损失，而且有些特性不能使用
 * 所以一般开发通用UDAF函数
 * <p>
 * 开发通用UDAF有两个步骤:
 * 第一个是编写resolver类，第二个是编写evaluator类。
 * resolver负责类型检查，操作符重载。
 * evaluator真正实现UDAF的逻辑。
 * 通常来说，顶层UDAF类继承{@link org.apache.hadoop.hive.ql.udf.generic.GenericUDAFResolver2}，
 * 里面编写嵌套类 evaluator 实现UDAF的逻辑。
 * <p>
 * resolver通常继承org.apache.hadoop.hive.ql.udf.GenericUDAFResolver2，但是更建议继承AbstractGenericUDAFResolver，隔离将来hive接口的变化。
 * GenericUDAFResolver和GenericUDAFResolver2接口的区别是:
 * 后面的允许evaluator实现利用GenericUDAFParameterInfo可以访问更多的信息，例如DISTINCT限定符，通配符(*)。
 *
 * hive> add jar /root/2017/udf.jar
 * hive> create temporary function mycount as 'com.study.hive.udaf.CountUDAF'
 * hive> select call, mycount(*) as cn from beauty group by call order by cn desc
 * hive> select tag, mycount(tag) as cn from beauty lateral view explode(tags) lve_beauty as tag group by tag order by cn desc
 */
public class CountUDAF extends AbstractGenericUDAFResolver {
    @Override
    public GenericUDAFEvaluator getEvaluator(TypeInfo[] params) throws SemanticException {
        //继承自 GenericUDAFResolver ，只能判断参数的个数类型
        if (params.length > 1) {
            throw new UDFArgumentLengthException("Exactly one argument is expected.");
        }
        return new CountUDAFEvaluator();
    }

    @Override
    public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info) throws SemanticException {
        // 继承自 GenericUDAFResolver2 ，还能判断 通配符(*) 、DISTINCT限定符
        ObjectInspector[] parameters = info.getParameterObjectInspectors();
        boolean isAllColumns = false;
        if (parameters.length == 0) {
            if (!info.isAllColumns()) {
                throw new UDFArgumentException("Argument expected");
            }
            if (info.isDistinct()) {
                throw new UDFArgumentException("DISTINCT not supported with *");
            }
            isAllColumns = true;
        } else if (parameters.length != 1) {
            throw new UDFArgumentLengthException("Exactly one argument is expected.");
        }
        return new CountUDAFEvaluator(isAllColumns);
    }
}
